﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BruteForceSerializer.Contract.Internal;
using BruteForceSerializer.Normalization;
using BruteForceSerializer.Tests.Testfälle_für_Normalisierung;
using NUnit.Framework;

namespace BruteForceSerializer.Tests
{
    [TestFixture]
    public class test_Normalizer
    {
        [Test]
        public void Null_Root_bleibt_null()
        {
            var no = Normalizer.Normalize(null);
            Assert.IsNull(no);
        }


        [Test]
        public void Normalisiertes_Objekt_mit_Typname_des_Originals()
        {
            var no = Normalizer.Normalize(new KlasseOhneFelder());
            Assert.AreSame(typeof(KlasseOhneFelder).Name, no.Typename);
        }


        [Test]
        public void Objekt_ohne_Felder()
        {
            var no = Normalizer.Normalize(new KlasseOhneFelder());
            Assert.AreEqual(0, no.Fields.Count());
        }

        [Test]
        public void Objekt_mit_skalaren_Feldern()
        {
            var no = Normalizer.Normalize(new KlasseMitSkalarenFeldern());
            Assert.That(no.Fields.Select(f => f.Name).ToArray(), Is.EqualTo(
                        new[]
                            {
                                "intField",
                                "stringField",
                                "doubleField",
                                "floatField",
                                "decimalField",
                                "dateTimeField",
                                "enumField"                                
                            }));

            Assert.That(no.Fields.Select(f => f.Value).ToArray(), Is.EqualTo(
                        new object[]
                            {
                                42,
                                "hello",
                                3.14,
                                3.14f,
                                decimal.Parse("3.14"),
                                new DateTime(2010, 5, 12, 12, 11, 10),
                                Farben.grün
                            }));
        }

        [Test]
        public void Null_Referenz_in_Feld()
        {
            var o = new KlasseMitSkalarenFeldern();
            o.stringField = null;

            var no = Normalizer.Normalize(o);

            Assert.IsNull(no.Fields.Where(f => f.Name == "stringField").Select(f => f.Value).First());
        }

        [Test]
        public void Objekt_mit_nicht_öffentlichen_Feldern()
        {
            var no = Normalizer.Normalize(new KlasseMitNichtÖffentlichenFeldern());
            Assert.That(no.Fields.Select(f => f.Name).ToArray(), Is.EqualTo(
                        new[]
                            {
                                "privateField",
                                "protectedField",
                                "internalField",
                            }));

            Assert.That(no.Fields.Select(f => f.Value).ToArray(), Is.EqualTo(
                        new object[]
                            {
                                42,
                                "hello",
                                3.14
                            }));
        }

        [Test]
        public void Objekt_mit_nicht_serialisierbarem_Feld()
        {
            var no = Normalizer.Normalize(new KlasseMitNichtSerialisierbaremFeld());
            Assert.AreEqual(0, no.Fields.Count());
        }


        [Test]
        public void Geschachtelte_Objekte()
        {
            var no = Normalizer.Normalize(new Parent());
            Assert.IsInstanceOf<INormalizedObject>(no.Fields.First().Value);
            Assert.AreEqual(typeof(Child).Name, ((INormalizedObject)no.Fields.First().Value).Typename);
        }

        [Test]
        public void Referenz_auf_geschachteltes_Object_ist_Null()
        {
            var p = new Parent();
            p.child = null;

            var no = Normalizer.Normalize(p);

            Assert.IsNull(no.Fields.First().Value);
        }

        [Test]
        public void Objekte_haben_IDs()
        {
            var no = Normalizer.Normalize(new Parent());

            Assert.AreEqual(0, no.Id);
            Assert.AreEqual(1, ((INormalizedObject)no.Fields.First().Value).Id);
        }

        [Test]
        public void Direkte_zirkuläre_Abhängigkeit()
        {
            var rootChild = new KlasseMitZirkulärerAbhängigkeit();
            rootChild.Next = rootChild;

            var no = Normalizer.Normalize(rootChild);

            var reference = (INormalizedReference)no.Fields.First().Value;
            Assert.AreEqual(0, reference.ObjectId);
        }
        
        [Test]
        public void Indirekte_zirkuläre_Abhängigkeit()
        {
            var root = new KlasseMitZirkulärerAbhängigkeit();
            var child = new KlasseMitZirkulärerAbhängigkeit();
            var grandchild = new KlasseMitZirkulärerAbhängigkeit();
            root.Next = child;
            child.Next = grandchild;
            grandchild.Next = child;

            var no = Normalizer.Normalize(root);

            var normalizedChild = (INormalizedObject)no.Fields.First().Value;
            var normalizedGrandchild = (INormalizedObject)normalizedChild.Fields.First().Value;
            var refChild = (INormalizedReference)normalizedGrandchild.Fields.First().Value;
            Assert.AreEqual(1, refChild.ObjectId);
        }

        [Test, Ignore]
        public void Objekt_mit_Delegaten()
        {
            Action action = () => { };

            var no = Normalizer.Normalize(action);

            //NOTE: nicht sehr erhellend, aber funktioniert
            foreach (var f in no.Fields)
                Console.WriteLine("{0}={1}", f.Name, f.Value);
        }

        [Test]
        public void Arrays_kodieren_Metadaten_beim_Typnamen()
        {
            Assert.AreEqual("Int32[]=0", Normalizer.Normalize(new int[]{}).Typename);
            Assert.AreEqual("Int32[]=3", Normalizer.Normalize(new[]{1,2,3}).Typename);

            Assert.AreEqual("Int32[,]=2,3", Normalizer.Normalize(new int[2,3] {{1,2,3}, {4,5,6}}).Typename);
        }

        [Test]
        public void Arrayelemente_als_Felder_interpretieren()
        {
            var no = Normalizer.Normalize(new[] {1, 2});

            Assert.That(no.Fields.Select(f => f.Name).ToArray(), Is.EqualTo(new[]{"[0]", "[1]"}));
            Assert.That(no.Fields.Select(f => f.Value).ToArray(), Is.EqualTo(new[] { 1, 2 }));
        }

        [Test]
        public void Mehrdimensionales_Array()
        {
            var no = Normalizer.Normalize(new[,] { { 1, 2 }, { 3, 4 } });

            Assert.That(no.Fields.Select(f => f.Name).ToArray(), Is.EqualTo(new[] { "[0]", "[1]", "[2]", "[3]" }));
            Assert.That(no.Fields.Select(f => f.Value).ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 }));
        }

        [Test]
        public void Ragged_Array()
        {
            var no = Normalizer.Normalize(new[] { new[] {1}, new[]{2,3} });

            Assert.AreEqual("Int32[][]=2", no.Typename);
            Assert.AreEqual("Int32[]=1", ((INormalizedObject) no.Fields.First().Value).Typename);
        }

        [Test]
        public void Struct_wird_behandelt_wie_ein_Objekt()
        {
            StructStattKlasse rec;
            rec.intField = 42;

            var no = Normalizer.Normalize(rec);

            Assert.AreEqual(42, no.Fields.First().Value);
        }

        [Test]
        public void IntPtr_wie_Int_behandeln()
        {
            var o = new KlasseMitIntPtr();

            var no = Normalizer.Normalize(o);

            Assert.IsInstanceOf<Int64>(no.Fields.First().Value);
            Assert.AreEqual(0, no.Fields.First().Value);
        }

        [Test]
        public void Fehlerreproduktion_Stringinhalt_wurde_nicht_in_Field_übersetzt_wenn_String_das_root_object_ist()
        {
            var no = Normalizer.Normalize("hello");
            Assert.IsInstanceOf<string>(no.Fields.First().Value);
            Assert.AreEqual("hello", no.Fields.First().Value);
        }
    }
}
